#include "NetworkBase.h"
#include "NetworkDefinitions.h"
#include "NetworkMessenger.h"
#include <stdio.h>
#include <assert.h>
#include <winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

#ifdef _DEBUG
	#include "../Utility/Misc/DebugOutput.h"
#endif

Address::Address() : address(0), port(0)
{
}

Address::Address(unsigned char _a, unsigned char _b, unsigned char _c, unsigned char _d, unsigned short _port) 
	: address((_a << 24) | (_b << 16) | (_c << 8) | _d), port(_port)
{
}

Address::Address(unsigned int _address, unsigned short _port) : address(_address), port(_port)
{
}

void Address::SetAddress(unsigned char _a, unsigned char _b, unsigned char _c, unsigned char _d, unsigned short _port)
{
	address = (_a << 24) | (_b << 16) | (_c << 8) | _d;
	port = _port;
}

bool Address::operator==(const Address& _rhs) const
{
	return address == _rhs.address && port == _rhs.port;
}
	
bool Address::operator!=(const Address& _rhs) const
{
	return !(*this == _rhs);
}

Socket::Socket() : socket(0)
{
}

Socket::~Socket()
{
	Close();
}

bool Socket::OpenClient(const Address& serverAddress)
{
	assert(!IsOpen());
#ifdef _DEBUG
	std::ostringstream msg;
	msg << "Creating socket on port: " << NET_DEFAULT_PORT;
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif

	// Create the socket
	socket = ::socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );

	if (socket < 0)
	{

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to create socket");
#endif

		socket = 0;
		return false;
	}

	unsigned long value = NET_NON_BLOCKING;
	if(ioctlsocket(socket, FIONBIO, &value) == SOCKET_ERROR)
	{
#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to set socket to non-blocking");
#endif

		return false;
	}


	return true;
}

bool Socket::OpenServer(unsigned short port)
{
	assert(!IsOpen());

#ifdef _DEBUG
	std::ostringstream msg;
	msg << "Creating socket on port: " << port;
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
		
	// Create the socket
	socket = ::socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );

	if (socket < 0)
	{

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to create socket");
#endif

		socket = 0;
		return false;
	}

#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Setting socket to Non-blocking");
#endif

	// Set to non-blocking
	DWORD nonBlocking = NET_NON_BLOCKING;
	if (ioctlsocket(socket, FIONBIO, &nonBlocking) != 0)
	{

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to set non-blocking socket");
#endif

		Close();
		return false;
	}

	// Bind the socket to the port
	sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons(port);

#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Binding the socket");
#endif
		
	HRESULT hr = bind(socket, (const sockaddr*)&address, sizeof(sockaddr_in));

	HRESULT hrError = WSAGetLastError();

	if (hr < 0)
	{

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to bind socket");
#endif

		Close();
		return false;
	}
		
	return true;
}

void Socket::Close()
{
	if (socket != 0)
	{
		closesocket(socket);
		socket = 0;
	}
}

bool Socket::IsOpen() const
{
	return socket != 0;
}
			
bool Socket::Send(const Address& destination, const void* data, int size)
{
	assert(data);
	assert(size > 0);
		
	if (socket == 0)
	{
		return false;
	}
		
	sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(destination.GetAddress());
	address.sin_port = htons((unsigned short)destination.GetPort());

	int bytesSent = sendto(socket, (const char*)data, size, 0, (sockaddr*)&address, sizeof(sockaddr_in));

	if(bytesSent != size)
	{
#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to send message");
#endif

		int error = WSAGetLastError();

		return false;
	}

	return true;
}

int Socket::Receive(Address& sender, void* data, int size)
{
	assert(data);
	assert(size > 0);
		
	if (socket == 0)
	{
		return false;
	}
			
	sockaddr_in incoming;
	int fromLength = sizeof(incoming);

	int bytesRead = recvfrom(socket, (char*)data, size, 0, (sockaddr*)&incoming, &fromLength);

	if (bytesRead <= 0)
	{
		return 0;
	}

	unsigned int address = ntohl(incoming.sin_addr.s_addr);
	unsigned int port = ntohs(incoming.sin_port);

	sender = Address(address, port);

	return bytesRead;
}

bool Socket::SetBroadcast()
{
	int option = NET_BROADCAST;
	if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&option, sizeof(option) == SOCKET_ERROR))
	{

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Broadcast setup error");
#endif

		return false;
	}

	return true;
}

bool InitializeSockets()
{
	WSADATA wsaData;
	int errorCode;

	errorCode = WSAStartup(MAKEWORD(2, 2), &wsaData);

	if (errorCode)
	{
#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to initialize socket");
#endif
		return false;
	}

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Sockets initialized");
#endif

	return true;
}

void ShutdownSockets()	
{
#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Shutting down sockets");
#endif
	WSACleanup();
}